#ifndef LOGGER_HPP
#define LOGGER_HPP

#include "format.hpp"
#include "sink.hpp"
#include "async.hpp"
#include <cstdlib>
#include <cassert>
#include <cstdarg>
#include <mutex>
#include <atomic>

namespace ly
{
    class Logger
    {
    public:
        using ptr = std::shared_ptr<Logger>;
        enum class Type
        {
            sync,
            async
        };

    public:
        Logger(const std::string &name, LogLevel::Level limit_level, Formatter::ptr formater, std::shared_ptr<std::vector<Sink::ptr>> sinks)
            : _name(name), _limit_level(limit_level), _formatter(formater), _sinks(sinks) {}
        void debug(const char *file, size_t line, const char *fmt, ...)
        {
            if (check_level(LogLevel::Level::debug) == false)
                return;
            va_list ap;
            va_start(ap, fmt);
            log(LogLevel::Level::debug, file, line, fmt, ap);
            va_end(ap);
        }
        void info(const char *file, size_t line, const char *fmt, ...)
        {
            if (check_level(LogLevel::Level::info) == false)
                return;
            va_list ap;
            va_start(ap, fmt);
            log(LogLevel::Level::info, file, line, fmt, ap);
            va_end(ap);
        }
        void warning(const char *file, size_t line, const char *fmt, ...)
        {
            if (check_level(LogLevel::Level::warning) == false)
                return;
            va_list ap;
            va_start(ap, fmt);
            log(LogLevel::Level::warning, file, line, fmt, ap);
            va_end(ap);
        }
        void error(const char *file, size_t line, const char *fmt, ...)
        {
            if (check_level(LogLevel::Level::error) == false)
                return;
            va_list ap;
            va_start(ap, fmt);
            log(LogLevel::Level::error, file, line, fmt, ap);
            va_end(ap);
        }
        void fatal(const char *file, size_t line, const char *fmt, ...)
        {
            if (check_level(LogLevel::Level::fatal) == false)
                return;
            va_list ap;
            va_start(ap, fmt);
            log(LogLevel::Level::fatal, file, line, fmt, ap);
            va_end(ap);
        }

        virtual ~Logger() = default;

    protected:
        virtual void log(LogLevel::Level level, const char *file, size_t line, const char *fmt, va_list ap) = 0;

        bool check_level(LogLevel::Level level) { return level >= _limit_level; }

    protected:
        std::string _name;
        LogLevel::Level _limit_level;
        Formatter::ptr _formatter;
        std::shared_ptr<std::vector<Sink::ptr>> _sinks;
    };

    class SyncLogger : public Logger
    {
    public:
        SyncLogger(const std::string &name, LogLevel::Level limit_level, Formatter::ptr formatter, std::shared_ptr<std::vector<Sink::ptr>> sink)
            : Logger(name, limit_level, formatter, sink) {}

    protected:
        virtual void log(LogLevel::Level level, const char *file, size_t line, const char *fmt, va_list ap) override
        {
            char *str = nullptr;
            if (-1 == vasprintf(&str, fmt, ap))
                return;
            LogMessage log_message(level, line, file, str, _name);
            free(str);

            std::string message = _formatter->format(log_message);
            for (auto &e : *_sinks)
                e->log(message.c_str(), message.size());
        }
    };
    class AsyncLogger : public Logger
    {
    public:
        AsyncLogger(const std::string &name, LogLevel::Level limit_level, Formatter::ptr formatter, std::shared_ptr<std::vector<Sink::ptr>> sink)
            : Logger(name, limit_level, formatter, sink) {}

    protected:
        virtual void log(LogLevel::Level level, const char *file, size_t line, const char *fmt, va_list ap) override
        {
            char *str = nullptr;
            if (-1 == vasprintf(&str, fmt, ap))
                return;
            LogMessage log_message(level, line, file, str, _name);
            free(str);

            auto message(std::make_shared<std::string>(_formatter->format(log_message)));
            AsyncLooper::getInstance().add_message({message, _sinks});
        }
    };

    template <typename T>
    class Builder
    {
    public:
        Builder &buildName(const std::string &name)
        {
            _name = name;
            return static_cast<T &>(*this);
        }
        Builder &buildType(Logger::Type type)
        {
            _type = type;
            return static_cast<T &>(*this);
        }
        Builder &buildLimitLevel(LogLevel::Level limit_level)
        {
            _limit_level = limit_level;
            return static_cast<T &>(*this);
        }
        template <typename SinkType, typename... Args>
        Builder &buildSink(Args &&...args)
        {
            auto sink = std::make_shared<SinkType>(std::forward<Args>(args)...);
            _sinks.push_back(sink);
            return static_cast<T &>(*this);
        }
        Builder &buildFormatPattern(const std::string &format_pattern)
        {
            _format_pattern = format_pattern;
            return static_cast<T &>(*this);
        }
        virtual Logger::ptr build() = 0;
        virtual ~Builder() = default;

    protected:
        Logger::ptr build_i()
        {
            assert(_name.size());

            Formatter::ptr formater;
            if (_format_pattern.size())
                formater = std::make_shared<Formatter>(_format_pattern);
            else
                formater = std::make_shared<Formatter>();

            std::shared_ptr<std::vector<Sink::ptr>> sinks = std::make_shared<std::vector<Sink::ptr>>(_sinks);

            if (Logger::Type::sync == _type)
                return std::make_shared<SyncLogger>(_name, _limit_level, formater, sinks);
            else
                return std::make_shared<AsyncLogger>(_name, _limit_level, formater, sinks);
        }

    protected:
        std::string _name;
        std::string _format_pattern;
        LogLevel::Level _limit_level;
        Logger::Type _type = Logger::Type::sync;
        std::vector<Sink::ptr> _sinks;
    };

    class LocalBuilder : public Builder<LocalBuilder>
    {
    public:
        virtual Logger::ptr build() { return build_i(); }
        virtual ~LocalBuilder() override {}
    };

    class LogManager
    {
    public:
        static LogManager &getInstance()
        {
            static LogManager manager;
            return manager;
        }

        void addLogger(const std::string &name, Logger::ptr logger)
        {
            std::lock_guard<std::mutex> lock(mutex);
            _loggers[name] = logger;
        }

        bool findLogger(const std::string &name)
        {
            return _loggers.count(name) != 0;
        }

        Logger::ptr getLogger(const std::string &name)
        {
            std::lock_guard<std::mutex> lock(mutex);
            return _loggers[name];
        }

    private:
        LogManager()
        {
            _loggers["default_logger"] = LocalBuilder()
                                            .buildName("default_logger")
                                            .buildSink<ly::StdoutSink>()
                                            .build();
        }
        LogManager(const LogManager &) = delete;

    private:
        std::mutex mutex;
        std::unordered_map<std::string, Logger::ptr> _loggers;
    };

    class GlobalBuilder : public Builder<LocalBuilder>
    {
    public:
        virtual Logger::ptr build()
        {
            auto ptr = build_i();
            LogManager::getInstance().addLogger(_name , ptr);
            return ptr;
        }
        virtual ~GlobalBuilder() override {}
    };
} // namespace ly

#endif // #define LOGGER_HPP